package org.jfsm.core;

import java.util.HashMap;

@SuppressWarnings("serial")
class BaseTransitionMap<C extends FSMContext> {

    static class TypeIDMap extends HashMap<Class<?>, Integer> {
    }

    static class EMap extends HashMap<Integer, Transition> {
    }

    static class SMap extends HashMap<Integer, EMap> {
    }

    FSMLogger logger;

    Transition[][] tMap;

    protected EMap defaultMap;
    protected Transition defaultTransition;

    private int stateIDCounter;

    private TypeIDMap sTypeMap;
    private SMap sMap;
    private SMap dsMap;

    private Enum<?> initState;
    private Enum<?> endState;

    public void setInitialState(Enum<?> state) {
        initState = state;
    }

    public void setTerminatingState(Enum<?> state) {
        endState = state;
    }

    public void setLogger(FSMLogger logger) {
        this.logger = logger;
    }

    /**
     * Define conditional transition from given state st1 to given state st2. If
     * the given signal occurs on state st1, built FSM will set context state to
     * st2 and call the given handler. FSM will continue processing if handler
     * returns a non-null signal.
     */
    public void put(Enum<?> st1, FSMSignal signal, Enum<?> st2, FSMSignalHandler<C> handler) {
        putT(st1, signal, st2, handler);
    }

    public void put(Enum<?> st1, FSMSignal signal, Enum<?> st2) {
        putT(st1, signal, st2, null);
    }

    public void remove(Enum<?> st1, FSMSignal signal) {
        removeT(st1, signal);
    }

    /**
     * Define conditional transition from given state st1 to given state st2. If
     * the a message of type M is received on state st1, built FSM will set
     * context state to st2 and call the given handler. FSM will continue
     * processing if handler returns a non-null event.
     * 
     * @param <M>
     *            Message type.
     * @param classM
     *            Class of the message type.
     */
    public <M> void put(Enum<?> st1, FSMSignalM<M> signal, Enum<?> st2,
            FSMSignalMHandler<C, ? super M> handler) {
        putT(st1, signal, st2, handler);
    }

    public void remove(Enum<?> st1, FSMSignalM<?> signal) {
        removeT(st1, signal);
    }

    /*
     * Define default transition for given state. If an event occurs on given
     * state and the transition map does not contain any entries for the
     * state/event pair, built FSM will set context state to st2.
     * 
     * public <E extends Enum<E>> void putDefault(Enum<?> st1, SignalGroup
     * group, Enum<?> st2, FSMSignalHandler<C> handler ) { Transition t = new
     * Transition( getSID(st1), getSID(st2) ); putDefaultTransition( st1, group,
     * st2, t ); }
     * 
     * public void removeDefault(Enum<?> st1, SignalGroup group) {
     * removeDefaultTransition( st1, group ); }
     */

    /**
     * Define default transition for given event on the FSM. If event occurs and
     * the transition map does not contain any entries for the current
     * state/event pair, built FSM will set context state to st2 and call the
     * given handler.
     */
    public void putDefault(FSMSignal signal, Enum<?> st2, FSMSignalHandler<C> handler) {
        putDT(signal, st2, handler);
    }

    public <M> void putDefault(FSMSignalM<M> signal, Enum<?> st2, FSMSignalMHandler<C, M> handler) {
        putDT(signal, st2, handler);
    }

    public <M> void putDefault(FSMSignalS<M> signal, Enum<?> st2, FSMSignalSHandler<C, M> handler) {
        putDT(signal, st2, handler);
    }

    public void removeDefault(FSMSignal signal) {
        removeDT(signal);
    }

    /**
     * Define default transition for the FSM. If an event occurs and no default
     * handler is set for the state/event pair, the transition map does not
     * contain any entries for the state/event pair, built FSM will set context
     * state to st2 and call the given handler.
     * 
     * @param st2
     *            The state to transition to, by default.
     * @param handler
     *            The default handler.
     */
    public void putDefault(Enum<?> st2, FSMSignalHandler<C> handler) {
        defaultTransition = new Transition(-1, getSID(st2), handler);
    }

    public void removeDefault() {
        defaultTransition = null;
    }

    public <M> void put(Enum<?> st1, FSMSignalS<M> signal, Enum<?> st2,
            FSMSignalSHandler<C, M> handler) {
        putT(st1, signal, st2, handler);
    }

    BaseTransitionMap() {
        logger = new FSMLogger();

        defaultTransition = new Transition();
        defaultMap = new EMap();

        sTypeMap = new TypeIDMap();
        sMap = new SMap();
        dsMap = new SMap();
    }

    void compile(int numSignals) throws FSMException {
        logger.setStateMap(stateIDCounter, sTypeMap);

        tMap = new Transition[stateIDCounter][];

        for (int st = 0; st < stateIDCounter; st++) {
            tMap[st] = new Transition[numSignals];
            EMap eMap = sMap.get(st);
            EMap dMap = dsMap.get(st);

            for (int id = 0; id < numSignals; id++) {
                Transition t = eMap.get(id);
                if (t == null && dMap != null) {
                    t = dMap.get(id);
                }
                if (t == null) {
                    t = defaultMap.get(id);
                }
                if (t == null) {
                    t = defaultTransition;
                }
                tMap[st][id] = t;
            }
        }
    }

    int getInitStateID() throws FSMException {
        if (initState == null) {
            throw new FSMException("Init state not defined.");
        }
        return getSID(initState);
    }

    int getEndStateID() throws FSMException {
        if (endState == null) {
            return -1;
        }
        return getSID(endState);
    }

    protected void putT(Enum<?> st1, FSMSignal signal, Enum<?> st2, Object handler) {
        int stid1 = getSID(st1);
        int stid2 = getSID(st2);

        Transition t = new Transition(stid1, stid2, handler);
        sMap.get(getSID(st1)).put(signal.getEid(), t);
    }

    protected void removeT(Enum<?> st1, FSMSignal signal) {
        EMap eMap = sMap.get(getSID(st1));
        eMap.remove(signal.getEid());
    }

    protected void putDT(FSMSignal signal, Enum<?> st2, Object handler) {
        int stid2 = getSID(st2);

        Transition t = new Transition(-1, stid2, handler);
        defaultMap.put(signal.getEid(), t);
    }

    protected void removeDT(FSMSignal signal) {
        defaultMap.remove(signal.getEid());
    }

    protected int getSID(Enum<?> state) {

        if (state == null) {
            return -1;
        }

        Class<?> clazz = state.getDeclaringClass();
        if (!sTypeMap.containsKey(clazz)) {
            sTypeMap.put(clazz, stateIDCounter);
            stateIDCounter += clazz.getEnumConstants().length;
        }
        int sid = sTypeMap.get(state.getDeclaringClass()) + state.ordinal();
        if (!sMap.containsKey(sid)) {
            sMap.put(sid, new EMap());
        }
        return sid;
    }

    @SuppressWarnings("unused")
    // For enter-state and leave-state handlers
    // Implementation is incomplete.
    private void checkDSMap(int stateID) {
        if (!dsMap.containsKey(stateID)) {
            dsMap.put(stateID, new EMap());
        }
    }

}
